<template>
  <div ref="tickerChartContainer" class="w-full h-full"></div>
</template>
<script setup>
import * as echarts from 'echarts';
import { ref, onMounted, onUnmounted } from 'vue';
/** @var {HTMLElement} */
const tickerChartContainer = ref(null);
/** @var {echart} */
let tickerChart = null;
/** @var {Object} */
let yAxisRange = {max: 0, min: 0};
/** @var {Array} */
let tickerChartData = [];
/** @var {Array} */
let tickerChartMarkPoints = [];
/** @var {Array} */
let tickerChartMarkLines = [];
/** @var {ResizeObserver} */
let tickerChartContainerObserver = null;
// on mounted
onMounted(mounted);
// on unmounted
onUnmounted(unmounted);
// define expose
defineExpose({appendData, markLineSet, reset, yAxisRangeAutoFit, markPointSet});

// on unmounted
function mounted() {
    tickerChart = echarts.init(tickerChartContainer.value);
    tickerChart.setOption(tickerChartGetSetupOptions());
    tickerChartContainerObserver = new ResizeObserver(() => {
        tickerChart.resize();
    });
    tickerChartContainerObserver.observe(tickerChartContainer.value);
}

// on unmounted
function unmounted() {
    if (tickerChartContainerObserver) {
        tickerChartContainerObserver.disconnect();
    }
    if (tickerChart) {
        tickerChart.dispose();
    }
}

// reset chart
function reset() {
    yAxisRange = {max: 0, min: 0};
    tickerChartData = [];
    tickerChartMarkPoints = [];
    tickerChartMarkLines = [];
    tickerChart.setOption(tickerChartGetSetupOptions());
}

// recompute chart yAxis range
function yAxisRangeAutoFit() {
    let max = -Infinity;
    let min = Infinity;

    for ( let item of tickerChartData ) {
        max = Math.max(max, item.value[1]);
        min = Math.min(min, item.value[1]);
    }
    for ( let item of tickerChartMarkLines ) {
        max = Math.max(max, item.yAxis);
        min = Math.min(min, item.yAxis);
    }
    yAxisRange.max = max;
    yAxisRange.min = min;

    max = max + max * 0.001;
    min = min - min * 0.001;
    tickerChart.setOption({yAxis : {min: min, max: max}});
}

// update chart yAxis range
function chartYAxisRangeUpdateByValue( value ) {
    if ( 0 === yAxisRange.max || value > yAxisRange.max ) {
        yAxisRange.max = value * 1;
    }
    if ( 0 === yAxisRange.min || value < yAxisRange.min ) {
        yAxisRange.min = value * 1;
    }
    let max = yAxisRange.max + yAxisRange.max * 0.001;
    let min = yAxisRange.min - yAxisRange.min * 0.001;
    tickerChart.setOption({yAxis : {min: min, max: max}});
}

// append data to chart
function appendData( price ) {
    let now = new Date();
    let time = [];
    time.push(now.getHours().toString().padStart('0',2));
    time.push(':');
    time.push(now.getMinutes().toString().padStart('0',2));
    time.push(':');
    time.push(now.getSeconds().toString().padStart('0',2));
    time.push('.');
    time.push(now.getMilliseconds().toString().padStart('0',3));
    time = time.join('');

    let timestamp = Date.now();
    tickerChartData.push({name:time, value:[timestamp, price]});

    // 移除5分钟之前的数据
    let nowTime = Date.now();
    let timeLimit = nowTime - 5 * 60 * 1000;
    tickerChartData = tickerChartData.filter(item => item.value[0] > timeLimit);

    tickerChartMarkPoints[0] = {coord:[timestamp, price],value:price, type:'last'};
    tickerChart.setOption({
        yAxis : {min: yAxisRange.min - 10,max: yAxisRange.max + 10},
        series: [{
            data: tickerChartData,
            markPoint: {data: tickerChartMarkPoints}
        }]
    });
    chartYAxisRangeUpdateByValue(price);
}

// set mark point
function markPointSet( id, x, y, options={}) {
    let point = {};
    point.coord = [x, y];
    point.value = y;
    point.symbolSize = 10;
    point.symbol = options.symbol || 'circle';
    point.itemStyle = {};
    point.itemStyle.color = options.color || 'red';

    tickerChartMarkPoints.push(point);
    tickerChart.setOption({series: [{markPoint: {data:tickerChartMarkPoints}}]});
}

// set mark line
function markLineSet( id, value, options={} ) {
    let line = tickerChartMarkLines.find(line => line.id === id);
    if ( undefined !== line && null === value ) { // remove line if value is null
        tickerChartMarkLines = tickerChartMarkLines.filter(line => line.id !== id);
        tickerChart.setOption({series: [{markLine: {data: tickerChartMarkLines}}]});
        return ;
    }

    if ( undefined === line ) {
        line = {};
        line.id = id;
        tickerChartMarkLines.push(line);
        line = tickerChartMarkLines[tickerChartMarkLines.length - 1];
    }
    line.yAxis = value;
    if ( undefined !== options.name ) {
        line.name = options.name;
    }
    if ( undefined !== options.color ) {
        line.lineStyle = line.lineStyle || {};
        line.lineStyle.color = options.color;
    }

    tickerChart.setOption({series: [{markLine: {data: tickerChartMarkLines}}]});
    chartYAxisRangeUpdateByValue(value);
}

// ticker chart get setup options
function tickerChartGetSetupOptions() {
    return {
        animation: false,
        grid: {top: 20, bottom: 20, left: 5, right: 100},
        xAxis: {type:'time',splitLine: {show: false}},
        yAxis: {type: 'value',scale: true,axisLabel: {inside: true,color: '#999'}},
        series: [{
            name: 'BTC/USDT',
            type: 'line',
            showSymbol: false,
            data: tickerChartData,
            lineStyle: {width: 1},
            markPoint: {
                symbolSize: 0,
                label: {show: true,position: 'right',color: '#333',formatter: params => {
                    if ( 'last' === params.data.type ) {
                        return params.value;
                    } else {
                        return '';
                    }
                }},
                data: tickerChartMarkPoints
            },
            markLine: {
                symbol: 'none',
                silent: true,
                lineStyle: {type: 'dashed',color: 'gray'},
                label: {show: true,position: 'end',formatter: params => `${params.data.name} : ${params.value}`},
                data: tickerChartMarkLines
            }
        }]
    };
}
</script>
